﻿Option Explicit On
Imports System.Xml

Public Class CandidateSetFinder
    ' James D. Zoll
    '
    ' The CandidateSetFinder class works closely with the CalcManager class
    ' as an object declared within the scope of a CalcManager
    ' to determine from a given list of items and parameters the combinations
    ' that result in possibly optimal sets of gear.  It uses an iterative hashing
    ' method to check only specific sets so that a multi threaded approach may be
    ' used.


    ' Variables to hold information about items.  This includes the item objects themselves
    ' in addition to information about various scoring metrics and statistics that
    ' are pre-calculated to increase the speed of the iteration.
    Private items(,) As Item
    Private itemNonCapScore(,) As Double
    Private itemNumSockets(,) As Integer
    Private numItems(0 To 12) As Integer
    Private maxNumItems As Integer

    ' Variables containing additional settings used in creating this calculation
    ' that are needed to make sure an accurate comparison is reached.  These are
    ' precalculated and passed from the parent scope.
    Private weights As Weight
    Private race As CalcManager.playerRace
    Private dwor2h As CalcManager.dwor2h
    Private forceTier(0 To 3) As Integer
    Private footEnchants() As CalcManager.Enchants
    Private wristEnchants() As CalcManager.Enchants
    Private handEnchants() As CalcManager.Enchants
    Private numFootE As Integer, numWristE As Integer, numHandE As Integer
    Private expRacialBonus(0 To 4) As Double
    Private numPieces As Integer
    Private ArPCap As Double
    Private HitCap As Double, SpellCap As Double
    Private ExpCapRating As Double
    Private forceHit As GemValidityMatrix.forceHitCap
    Private forceExp As Boolean
    Private minHit As Integer
    Private minExp As Integer
    Private primaryTree As String

    ' Variables relating to iteration.  The goal is to check all
    ' combinations with IDs from startID to endID. The number of permutations
    ' for each slot is required to accurately de-hash the IDs.
    Private startID As Double, endID As Double
    Private numPerms() As Integer

    ' Variables calculated pre-iteration to
    ' determine how to best approximate gemming
    ' when determining minimum and maximum scores
    ' for a given set.
    Private bestNonCapGemScore As Double
    Private bestNonCapWeight As Double

    ' Variables used during iteration to represent the current and
    ' previous sets, the current value of the set being tested,
    ' and other score related information.
    Private itemIDs() As Integer
    Private lastIDs() As Integer
    Private totalStats(0 To 8) As Double
    Private preEvalScore As Double
    Private numGemSockets As Integer

    ' The best score against which all tested sets are judged.
    ' Incremented when a new best is found.
    Private bestMinScore As Double

    ' The three events for CandidateSetFinder. These are primarily used as
    ' flow control and progres update for the parent scope.
    Public Event AddCandidateSet(ByVal itemIDs() As Integer, ByVal minScore As Integer, ByVal maxScore As Integer, ByVal wEnch As CalcManager.Enchants, ByVal hEnch As CalcManager.Enchants, ByVal fEnch As CalcManager.Enchants)
    Public Event calcFinished()
    Public Event SetChecked(ByVal numSets As Double)

    ' Boolean Variables for internally modeled items.
    ' Items with weird procs or rings / weapons with procs are done internally since
    ' the user interface does not support it.
    Private BrynTrollH As Boolean
    Private BrynTrollN As Boolean
    Private AshenRingE As Boolean
    Private DeathbringersWillH As Boolean
    Private DeathbringersWillN As Boolean
    Private Shadowmourne As Boolean

    Private BrynTrollHValue As Double
    Private BrynTrollNValue As Double
    Private AshenRingEValue As Double = 0
    Private DeathbringersWillHValue As Double
    Private DeathbringersWillHUptime As Double
    Private DeathbringersWillHArP As Boolean
    Private DeathbringersWillNValue As Double
    Private DeathbringersWillNUptime As Double
    Private DeathbringersWillNArP As Boolean
    Private ShadowmourneValue As Double

    Enum stats
        str
        ap
        crit
        haste
        arp
        agi
        hit
        exp
        armor
    End Enum

    Enum slotIdx
        head
        shoulder
        chest
        hand
        leg
        neck
        back
        wrist
        waist
        foot
        finger1
        finger2
        trinket1
        trinket2
        weapon1
        weapon2
    End Enum

    Public Sub setData(ByVal inItems(,) As Item, ByVal inNumItems() As Integer, ByVal inMaxItems As Integer, ByVal inWeights As Weight, ByVal inRace As CalcManager.playerRace, _
                        ByVal inDWor2H As CalcManager.dwor2h, ByVal inForceTier() As Integer, ByVal inFE() As CalcManager.Enchants, ByVal inWE() As CalcManager.Enchants, _
                        ByVal inHE() As CalcManager.Enchants, ByVal numFE As Integer, ByVal numWE As Integer, ByVal numHE As Integer, ByVal inExpRacial() As Double, _
                        ByVal inArPCap As Double, ByVal inHitCap As Double, ByVal inSpellCap As Double, ByVal inExpCapRating As Double, ByVal forceHitIN As GemValidityMatrix.forceHitCap, ByVal forceExpIN As Boolean, ByVal primarytreeIN As String)
        ' This sub is used to gather the required data from the parent scope in order
        ' to be able to process and find candidate sets correctly.  Item and gem
        ' variables are also redefined to have the appropriate dimensions.
        Try
            ReDim items(0 To 12, 0 To inMaxItems - 1)
            ReDim itemNonCapScore(0 To 12, inMaxItems - 1)
            ReDim itemNumSockets(0 To 12, inMaxItems - 1)
            items = inItems.Clone
            numItems = inNumItems
            maxNumItems = inMaxItems
            weights = inWeights
            race = inRace
            dwor2h = inDWor2H
            forceTier = inForceTier
            numFootE = numFE
            numWristE = numWE
            numHandE = numHE
            ReDim footEnchants(0 To numFootE - 1)
            ReDim wristEnchants(0 To numWristE - 1)
            ReDim handEnchants(0 To numHandE - 1)
            footEnchants = inFE
            wristEnchants = inWE
            handEnchants = inHE
            expRacialBonus = inExpRacial
            ArPCap = inArPCap
            HitCap = inHitCap
            SpellCap = inSpellCap
            ExpCapRating = inExpCapRating
            forceHit = forceHitIN
            forceExp = forceExpIN
            primaryTree = primarytreeIN
            Select Case forceHit
                Case GemValidityMatrix.forceHitCap.none
                    minHit = 0
                Case GemValidityMatrix.forceHitCap.melee
                    minHit = System.Math.Ceiling(HitCap)
                Case GemValidityMatrix.forceHitCap.spell
                    minHit = System.Math.Ceiling(SpellCap)
            End Select
            If forceExp = True Then
                minExp = System.Math.Ceiling(ExpCapRating)
            Else
                minExp = 0
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setStart(ByVal startIDin As Double, ByVal endIDin As Double, ByVal numPermsIN() As Integer)
        ' This procedure exists to take the starting and ending information from the
        ' parent scope.  This is necessary to be able to accurately iterate.
        Try
            startID = startIDin
            endID = endIDin
            numPerms = numPermsIN
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub findSets()
        ' Threaded execution of the CandidateSetFinder begins here.
        ' Several methods of gear selection are planned, so only the currently
        ' used line will be uncommented.
        findSetsHash()
        'findSetsRecurse()

    End Sub

    Private Sub findSetsHash()
        ' This is the primary process for the
        ' hash method of finding candidate sets for gemming.  It iterates
        ' through all the designated sets after doing some initial setup
        ' that is necessary to make sure to do as little work as possible
        ' while actually inside the loop.

        ' General variable declaration of loop control variables and
        ' modFactor, which will hold values that help us de-hash the setIDs.
        ' setIncrement is used for progress management.
        Try
            Dim loopvar As Double, loopvar2 As Double
            Dim slotIndex As Double
            Dim Floop As Integer, Wloop As Integer, Hloop As Integer
            Dim modFactor(0 To 12) As Double
            Dim setIncrement As Double

            ' Set setIncrement to zero, indicating that zero sets
            ' have been processed.
            setIncrement = 0

            ' Calculate the value of numPieces according to the DW or 2H flag,
            ' which will be used to generate permutation numbers for dehashing.
            ' Additionally, redefine the dimensions of the arrays that are controlled
            ' by the number of pieces.
            If dwor2h = CalcManager.dwor2h.twohand Then
                numPieces = 15
            Else
                numPieces = 16
            End If
            ReDim itemIDs(0 To numPieces - 1)
            ReDim lastIDs(0 To numPieces - 1)

            For loopvar = 0 To numPieces - 1
                lastIDs(loopvar) = -1
            Next

            ' Score zeroing.  Before calculations begin all
            ' scores must be zeroed out.
            For loopvar = 0 To 8
                totalStats(loopvar) = 0
            Next
            numGemSockets = 0
            preEvalScore = 0
            bestMinScore = 0

            ' Internal items.  Initially all items are set to false.
            BrynTrollH = False
            BrynTrollN = False
            AshenRingE = False
            DeathbringersWillH = False
            DeathbringersWillN = False
            Shadowmourne = False

            ' Internal Items - Calculate the weighted value of these procs.
            calculateInternalProcs()

            ' ModFactor is used to determine the frequency with which
            ' items in each slot both repeat and change. This is the primary
            ' way that we can dehash a setID into item IDs for each slot.
            For loopvar = 0 To 12
                modFactor(loopvar) = 1
                For loopvar2 = loopvar + 1 To 12
                    modFactor(loopvar) *= numPerms(loopvar2)
                Next
            Next

            ' For non-single slot items like rings, trinkets, and weapons,
            ' build an array representing the possible combinations
            ' of these items before beginning iteration.
            Dim ringCombo(,) As Integer
            Dim weaponCombo(,) As Integer
            Dim trinketCombo(,) As Integer
            Dim curCombo As Double

            ReDim ringCombo(0 To numPerms(CalcManager.itemIdx.finger) - 1, 0 To 1)
            curCombo = 0
            ReDim ringCombo(0 To numPerms(CalcManager.itemIdx.finger) - 1, 0 To 1)
            For loopvar = 0 To numItems(CalcManager.itemIdx.finger) - 1
                If items(CalcManager.itemIdx.finger, loopvar).unique = False Then
                    ringCombo(curCombo, 0) = loopvar : ringCombo(curCombo, 1) = loopvar
                    curCombo += 1
                End If
                For loopvar2 = loopvar + 1 To numItems(CalcManager.itemIdx.finger) - 1
                    ringCombo(curCombo, 0) = loopvar : ringCombo(curCombo, 1) = loopvar2
                    curCombo += 1
                Next
            Next
            ReDim trinketCombo(0 To numPerms(CalcManager.itemIdx.trinket) - 1, 0 To 1)
            curCombo = 0
            For loopvar = 0 To numItems(CalcManager.itemIdx.trinket) - 1
                For loopvar2 = loopvar + 1 To numItems(CalcManager.itemIdx.trinket) - 1
                    trinketCombo(curCombo, 0) = loopvar : trinketCombo(curCombo, 1) = loopvar2
                    curCombo += 1
                Next
            Next
            If dwor2h = CalcManager.dwor2h.twohand Then
                ReDim weaponCombo(0 To numPerms(CalcManager.itemIdx.weapon) - 1, 0 To 0)
                curCombo = 0
                For loopvar = 0 To numItems(CalcManager.itemIdx.weapon) - 1
                    weaponCombo(curCombo, 0) = loopvar
                    curCombo += 1
                Next
            Else
                ReDim weaponCombo(0 To numPerms(CalcManager.itemIdx.weapon) - 1, 0 To 1)
                curCombo = 0
                For loopvar = 0 To numItems(CalcManager.itemIdx.weapon) - 1
                    If items(CalcManager.itemIdx.weapon, loopvar).unique = False And items(CalcManager.itemIdx.weapon, loopvar).wephand = Item.wephands.onehand Then
                        weaponCombo(curCombo, 0) = loopvar : weaponCombo(curCombo, 1) = loopvar
                        curCombo += 1
                    End If
                    For loopvar2 = loopvar + 1 To numItems(CalcManager.itemIdx.weapon) - 1
                        Select Case items(CalcManager.itemIdx.weapon, loopvar).wephand
                            Case Item.wephands.mainhand
                                If items(CalcManager.itemIdx.weapon, loopvar2).wephand = Item.wephands.offhand Or items(CalcManager.itemIdx.weapon, loopvar2).wephand = Item.wephands.onehand Then
                                    weaponCombo(curCombo, 0) = loopvar : weaponCombo(curCombo, 1) = loopvar2
                                    curCombo += 1
                                End If
                            Case Item.wephands.offhand
                                If items(CalcManager.itemIdx.weapon, loopvar2).wephand = Item.wephands.mainhand Or items(CalcManager.itemIdx.weapon, loopvar2).wephand = Item.wephands.onehand Then
                                    weaponCombo(curCombo, 0) = loopvar2 : weaponCombo(curCombo, 1) = loopvar
                                    curCombo += 1
                                End If
                            Case Item.wephands.onehand
                                Select Case items(CalcManager.itemIdx.weapon, loopvar2).wephand
                                    Case Item.wephands.onehand
                                        weaponCombo(curCombo, 0) = loopvar : weaponCombo(curCombo, 1) = loopvar2
                                        curCombo += 1
                                        weaponCombo(curCombo, 0) = loopvar2 : weaponCombo(curCombo, 1) = loopvar
                                        curCombo += 1
                                    Case Item.wephands.mainhand
                                        weaponCombo(curCombo, 0) = loopvar2 : weaponCombo(curCombo, 1) = loopvar
                                        curCombo += 1
                                    Case Item.wephands.offhand
                                        weaponCombo(curCombo, 0) = loopvar : weaponCombo(curCombo, 1) = loopvar2
                                        curCombo += 1
                                End Select
                        End Select
                    Next
                Next
            End If

            ' In order to accurately find minima and maxima for candidate sets
            ' it is necessary to know the value of the best uncappable stat, and
            ' the value of a single gem of this stat.
            Dim gemValue(0 To 4) As Double

            gemValue(0) = weights.str
            gemValue(1) = weights.haste
            gemValue(2) = weights.haste
            gemValue(3) = weights.arp
            gemValue(4) = weights.ap
            bestNonCapGemScore = 0
            For loopvar = 0 To 4
                If gemValue(loopvar) * 20 > bestNonCapGemScore Then
                    bestNonCapGemScore = gemValue(loopvar) * 20
                    bestNonCapWeight = gemValue(loopvar)
                End If
            Next

            ' In order to reduce calculation during iteration and looping, find the
            ' total score for non cappable stats for every item so that this can be added
            ' or subtracted rather than doing 6 separate operations.  Additionally populate the
            ' number of sockets for the item so it is not found dynamically.
            For loopvar = 0 To 12
                For loopvar2 = 0 To numItems(loopvar) - 1
                    itemNonCapScore(loopvar, loopvar2) = 0
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).agi * weights.agi
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).ap * weights.ap
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).armor * weights.armor
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).crit * weights.crit
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).haste * weights.haste
                    itemNonCapScore(loopvar, loopvar2) += items(loopvar, loopvar2).str * weights.str
                    itemNumSockets(loopvar, loopvar2) = numSockets(items(loopvar, loopvar2))
                Next
            Next

            ' Iterate through all IDs that this thread is assigned to.
            ' For each ID determine if it is valid and iterate all enchants.
            ' At that point, call the set evaluation routine to determine if it
            ' should be treated as a valid candidate set.  Additionally some progress
            ' variables are tracked when sets are checked.
            For loopvar = startID To endID
                ' Use the modulo factors that were previously calculated combined with the known complexity
                ' of each slot to determine the set for the setID being calculated.  For multiple slot
                ' items like rings, trinkets, and weapons this is done sequentially below.  Additionally
                ' the process is short circuited if set requirements are not met.
                For slotIndex = 0 To slotIdx.foot
                    itemIDs(slotIndex) = Int((loopvar / modFactor(slotIndex)) Mod numPerms(slotIndex))
                Next
                If meetsSetReqs() Then
                    curCombo = Int((loopvar / modFactor(CalcManager.itemIdx.finger)) Mod numPerms(10))
                    itemIDs(slotIdx.finger1) = ringCombo(curCombo, 0)
                    itemIDs(slotIdx.finger2) = ringCombo(curCombo, 1)
                    curCombo = Int((loopvar / modFactor(CalcManager.itemIdx.trinket)) Mod numPerms(11))
                    itemIDs(slotIdx.trinket1) = trinketCombo(curCombo, 0)
                    itemIDs(slotIdx.trinket2) = trinketCombo(curCombo, 1)
                    curCombo = Int((loopvar / modFactor(CalcManager.itemIdx.weapon)) Mod numPerms(12))
                    If dwor2h = CalcManager.dwor2h.dualwield Then
                        itemIDs(slotIdx.weapon1) = weaponCombo(curCombo, 0)
                        itemIDs(slotIdx.weapon2) = weaponCombo(curCombo, 1)
                    Else
                        itemIDs(slotIdx.weapon1) = weaponCombo(curCombo, 0)
                    End If

                    ' In order to reduce computation time, stat changes are only made for 
                    ' items that have changed since the previous set.  When this is done, iterate
                    ' through the available enchants and then evaluate the full value of the set
                    ' by calling evalSet.
                    For loopvar2 = 0 To numPieces - 1
                        If itemIDs(loopvar2) <> lastIDs(loopvar2) Then
                            changeItem(itemIDs(loopvar2), lastIDs(loopvar2), loopvar2)
                        End If
                    Next
                    For Floop = 0 To numFootE - 1
                        For Wloop = 0 To numWristE - 1
                            For Hloop = 0 To numHandE - 1
                                evalSet(footEnchants(Floop), wristEnchants(Wloop), handEnchants(Hloop))
                            Next
                        Next
                    Next
                    ' Set the lastIDs to the current IDs.  Note that this only happens if we dropped
                    ' into the loop that meets set requirements as
                    ' we don't actually change any stat values if we don't.
                    For loopvar2 = 0 To numPieces - 1
                        lastIDs(loopvar2) = itemIDs(loopvar2)
                    Next
                End If
                ' After checking a set, whether or not it fits the set IDs,
                ' increment the number of sets checked and raise the event
                ' if this is above the threshold to report so that we
                ' don't choke on events.
                setIncrement += 1
                If setIncrement > 500 Then
                    RaiseEvent SetChecked(setIncrement)
                    setIncrement = 0
                End If
            Next

            ' We've iterated through all of our setIDs, so this thread is done.
            ' Raise the calcFinished event to return control to the parent scope.
            ' No data needs to be passed at this time, as it was all passed during
            ' iteration.
            RaiseEvent calcFinished()
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub changeItem(ByVal newIndex As Integer, ByVal oldIndex As Integer, ByVal slotIndex As slotIdx)
        ' This procedure alters the preEvalScore variable to subtract the score of the
        ' piece indicated by oldIndex and add the score of the piece indicated by
        ' newindex.  If oldIndex is -1 then this is the first time a score has been
        ' added, so nothing is subtracted.  ArP, Hit, and Expertise are still calculated manually
        ' since they can affect the value of trinket procs and stat caps during setEval()
        Try
            Dim originalIndex As slotIdx

            originalIndex = slotIndex
            If slotIndex = slotIdx.finger2 Or slotIndex = slotIdx.finger1 Then
                slotIndex = 10
            ElseIf slotIndex = slotIdx.trinket2 Or slotIndex = slotIdx.trinket1 Then
                slotIndex = 11
            ElseIf slotIndex = slotIdx.weapon2 Or slotIndex = slotIdx.weapon1 Then
                slotIndex = 12
            End If

            If oldIndex <> -1 Then
                preEvalScore = preEvalScore - itemNonCapScore(slotIndex, oldIndex) + itemNonCapScore(slotIndex, newIndex)
                totalStats(stats.arp) = totalStats(stats.arp) - items(slotIndex, oldIndex).arp + items(slotIndex, newIndex).arp
                totalStats(stats.hit) = totalStats(stats.hit) - items(slotIndex, oldIndex).hit + items(slotIndex, newIndex).hit
                totalStats(stats.exp) = totalStats(stats.exp) - items(slotIndex, oldIndex).exp + items(slotIndex, newIndex).exp
                If originalIndex = slotIdx.weapon1 Then
                    preEvalScore = preEvalScore - (items(slotIndex, oldIndex).wepdps * weights.mhdps) + (items(slotIndex, newIndex).wepdps * weights.mhdps)
                    preEvalScore = preEvalScore - (items(slotIndex, oldIndex).wepspeed * weights.mhspeed) + (items(slotIndex, newIndex).wepspeed * weights.mhspeed)
                    totalStats(stats.exp) = totalStats(stats.exp) - expRacialBonus(items(slotIndex, oldIndex).weptype) + expRacialBonus(items(slotIndex, newIndex).weptype)
                ElseIf originalIndex = slotIdx.weapon2 Then
                    preEvalScore = preEvalScore - (items(slotIndex, oldIndex).wepdps * weights.ohdps) + (items(slotIndex, newIndex).wepdps * weights.ohdps)
                    preEvalScore = preEvalScore - (items(slotIndex, oldIndex).wepspeed * weights.ohspeed) + (items(slotIndex, newIndex).wepspeed * weights.ohspeed)
                End If
                numGemSockets = numGemSockets - itemNumSockets(slotIndex, oldIndex) + itemNumSockets(slotIndex, newIndex)

                ' Internally modeled items. Some can be done here
                ' if they add a static score.  Others must be done
                ' later if they involve cappable stats.
                ' Each item will have a static 'value' for damage procs / etc
                ' followed by math for stat based values.
                Select Case items(slotIndex, oldIndex).name
                    Case "Bryntroll, the Bone Arbiter (Heroic)"
                        BrynTrollH = False
                        preEvalScore = preEvalScore - BrynTrollHValue
                        ' No cappable stats to model.
                    Case "Bryntroll, the Bone Arbiter"
                        BrynTrollN = False
                        preEvalScore = preEvalScore - BrynTrollNValue
                        ' No cappable stats to model.
                    Case "Deathbringer's Will (Heroic)"
                        DeathbringersWillH = False
                        preEvalScore = preEvalScore - DeathbringersWillHValue
                        ' Need to model cappable stats later.
                    Case "Deathbringer's Will"
                        DeathbringersWillN = False
                        preEvalScore = preEvalScore - DeathbringersWillNValue
                        ' Need to model cappable stats later.
                    Case "Ashen Band of Endless Vengeance"
                        AshenRingE = False
                        preEvalScore = preEvalScore - AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Vengeance"
                        AshenRingE = False
                        preEvalScore = preEvalScore - AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Endless Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore - AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore - AshenRingEValue
                        ' No cappable stats to model.
                    Case "Shadowmourne"
                        Shadowmourne = False
                        preEvalScore = preEvalScore - ShadowmourneValue
                        ' No cappable stats to model.
                End Select

                Select Case items(slotIndex, newIndex).name
                    Case "Bryntroll, the Bone Arbiter (Heroic)"
                        BrynTrollH = True
                        preEvalScore = preEvalScore + BrynTrollHValue
                        ' No cappable stats to model.
                    Case "Bryntroll, the Bone Arbiter"
                        BrynTrollN = True
                        preEvalScore = preEvalScore + BrynTrollNValue
                        ' No cappable stats to model.
                    Case "Deathbringer's Will (Heroic)"
                        DeathbringersWillH = True
                        preEvalScore = preEvalScore + DeathbringersWillHValue
                        ' Need to model non-capped stats later.
                    Case "Deathbringer's Will"
                        DeathbringersWillN = True
                        preEvalScore = preEvalScore + DeathbringersWillNValue
                        ' Need to model non-capped stats later.
                    Case "Ashen Band of Endless Vengeance"
                        AshenRingE = True
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Vengeance"
                        AshenRingE = True
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Endless Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Shadowmourne"
                        Shadowmourne = True
                        preEvalScore = preEvalScore + ShadowmourneValue
                        ' No cappable stats to model.
                End Select

            Else
                preEvalScore = preEvalScore + itemNonCapScore(slotIndex, newIndex)
                totalStats(stats.arp) = totalStats(stats.arp) + items(slotIndex, newIndex).arp
                totalStats(stats.hit) = totalStats(stats.hit) + items(slotIndex, newIndex).hit
                totalStats(stats.exp) = totalStats(stats.exp) + items(slotIndex, newIndex).exp
                If originalIndex = slotIdx.weapon1 Then
                    preEvalScore = preEvalScore + (items(slotIndex, newIndex).wepdps * weights.mhdps)
                    preEvalScore = preEvalScore + (items(slotIndex, newIndex).wepspeed * weights.mhspeed)
                    totalStats(stats.exp) = totalStats(stats.exp) + expRacialBonus(items(slotIndex, newIndex).weptype)
                ElseIf originalIndex = slotIdx.weapon2 Then
                    preEvalScore = preEvalScore + (items(slotIndex, newIndex).wepdps * weights.ohdps)
                    preEvalScore = preEvalScore + (items(slotIndex, newIndex).wepspeed * weights.ohspeed)
                End If
                numGemSockets = numGemSockets + itemNumSockets(slotIndex, newIndex)

                ' Internally modeled items
                Select Case items(slotIndex, newIndex).name
                    Case "Bryntroll, the Bone Arbiter (Heroic)"
                        BrynTrollH = True
                        preEvalScore = preEvalScore + BrynTrollHValue
                        ' No cappable stats to model.
                    Case "Bryntroll, the Bone Arbiter"
                        BrynTrollN = True
                        preEvalScore = preEvalScore + BrynTrollNValue
                        ' No cappable stats to model.
                    Case "Deathbringer's Will (Heroic)"
                        DeathbringersWillH = True
                        preEvalScore = preEvalScore + DeathbringersWillHValue
                        ' Need to model non-capped stats later.
                    Case "Deathbringer's Will"
                        DeathbringersWillN = True
                        preEvalScore = preEvalScore + DeathbringersWillNValue
                        ' Need to model non-capped stats later.
                    Case "Ashen Band of Endless Vengeance"
                        AshenRingE = True
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Vengeance"
                        AshenRingE = True
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Endless Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Ashen Band of Unmatched Might"
                        AshenRingE = False
                        preEvalScore = preEvalScore + AshenRingEValue
                        ' No cappable stats to model.
                    Case "Shadowmourne"
                        Shadowmourne = True
                        preEvalScore = preEvalScore + ShadowmourneValue
                        ' No cappable stats to model.
                End Select
            End If


        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Function numSockets(ByVal inItem As Item) As Integer
        ' numSockets returns as an integer the number of gemsockets
        ' that an item has that are not 'None'.  The sequential ordering
        ' of gem sockets in all items is assumed.
        Try
            If inItem.socketcolor(2) <> Item.colors.none Then
                numSockets = 3
            ElseIf inItem.socketcolor(1) <> Item.colors.none Then
                numSockets = 2
            ElseIf inItem.socketcolor(0) <> Item.colors.none Then
                numSockets = 1
            Else
                numSockets = 0
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function meetsSetReqs() As Boolean
        ' meetsSetReqs checked the current set of itemIDs
        ' to determine if the referenced items meet the forced Tier
        ' set requirements passed to CandidateSetFinder.  It does this
        ' by iterating through the first five items and checked against 
        ' predefined set strings.
        Try
            Dim setString(0 To 3) As String
            Dim loopvar As Integer, loopvar2 As Integer
            Dim numFound(0 To 3) As Integer

            setString(0) = "Scourgeborne"
            setString(1) = "Darkruned"
            setString(2) = "Koltira's"
            setString(3) = "Scourgelord "
            For loopvar = 0 To 3
                numFound(loopvar) = 0
                For loopvar2 = slotIdx.head To slotIdx.leg
                    If InStr(items(loopvar2, itemIDs(loopvar2)).name, setString(loopvar)) <> 0 Then numFound(loopvar) += 1
                Next
                If numFound(loopvar) < forceTier(loopvar) Then
                    meetsSetReqs = False
                    Exit Function
                End If
            Next
            meetsSetReqs = True
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function setBonusScore() As Double
        ' This function returns a double representing the total score
        ' from set bonuses for a given set of items referenced by itemIDs.
        ' This iterates very similarly to meetsSetReqs.
        Try
            Dim setString(0 To 3) As String
            Dim loopvar As Integer, loopvar2 As Integer
            Dim numFound(0 To 3) As Integer
            Dim Score As Double

            setString(0) = "Scourgeborne"
            setString(1) = "Darkruned"
            setString(2) = "Koltira's"
            setString(3) = "Scourgelord "
            For loopvar = 0 To 3
                numFound(loopvar) = 0
                For loopvar2 = slotIdx.head To slotIdx.leg
                    If InStr(items(loopvar2, itemIDs(loopvar2)).name, setString(loopvar)) <> 0 Then numFound(loopvar) += 1
                Next
            Next
            Score = 0
            If numFound(0) >= 4 Then Score += weights.tier(0, 1)
            If numFound(0) >= 2 Then Score += weights.tier(0, 0)
            If numFound(1) >= 4 Then Score += weights.tier(1, 1)
            If numFound(1) >= 2 Then Score += weights.tier(1, 0)
            If numFound(2) >= 4 Then Score += weights.tier(2, 1)
            If numFound(2) >= 2 Then Score += weights.tier(2, 0)
            If numFound(3) >= 4 Then Score += weights.tier(3, 1)
            If numFound(3) >= 2 Then Score += weights.tier(3, 0)
            setBonusScore = Score
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Sub evalSet(ByVal fEnch As CalcManager.Enchants, ByVal wEnch As CalcManager.Enchants, ByVal hEnch As CalcManager.Enchants)
        ' evalSet finalizes the minimum and maximum possible scores for a given candidate
        ' set.  It does here all of the things that are dependent on enchants or on trinket / hit / exp
        ' ArP values.  If the score at the end is sufficient then a new candidate is sent back to 
        ' the parent scope via an event.

        ' Generic Variable definition.  We will use myTotalStats to edit stats
        ' without affecting preEvalScore or totalStats so that we don't have to
        ' decrement them time consumingly at the end of this section.
        Try
            Dim totalScore As Double
            Dim myTotalStats(0 To 8) As Double
            Dim loopvar As Integer

            ' Set totalscore and totalstats to local copies that can
            ' be edited here.
            totalScore = preEvalScore
            For loopvar = 0 To 8
                myTotalStats(loopvar) = totalStats(loopvar)
            Next

            ' For each of the three variable enchants, add the appropriate score or cappable
            ' stats to the local copies of the variables.
            Select Case fEnch
                Case CalcManager.Enchants.foot_GreaterAssault
                    totalScore += (32 * weights.ap)
                Case CalcManager.Enchants.foot_Icewalker
                    totalScore += (12 * weights.crit) : myTotalStats(stats.hit) += 12
                Case CalcManager.Enchants.foot_NitroBoosts
                    totalScore += (24 * weights.crit)
            End Select

            Select Case wEnch
                Case CalcManager.Enchants.wrist_Expertise
                    myTotalStats(stats.exp) += 15
                Case CalcManager.Enchants.wrist_GreaterAssault
                    totalScore += (50 * weights.ap)
                Case CalcManager.Enchants.wrist_FurLining
                    totalScore += (130 * weights.ap)
            End Select

            Select Case hEnch
                Case CalcManager.Enchants.hand_Crusher
                    totalScore += (44 * weights.ap)
                Case CalcManager.Enchants.hand_HyperspeedAccelerators
                    totalScore += (340 * weights.haste) * (12 / 60)
                Case CalcManager.Enchants.hand_MajorStrength
                    totalScore += (15 * weights.str)
                Case CalcManager.Enchants.hand_Precision
                    myTotalStats(stats.hit) += 20
            End Select

            ' Add the score from ArP.  As of WoW patch 3.3 (Icecrown Citadel Raid)
            ' it is possible to hard cap ArP from gear / gems alone for some specs,
            ' so track the cap here.
            If myTotalStats(stats.arp) > ArPCap Then
                totalScore += ArPCap * weights.arp
            Else
                totalScore += (myTotalStats(stats.arp) * weights.arp)
            End If

            ' Scores for Tier bonuses of the set are added by the setBonusScore function.
            totalScore += setBonusScore()

            ' Score for all internally modified items with cappable stat procs needs to be added.
            If DeathbringersWillH = True And DeathbringersWillHArP = True Then
                If myTotalStats(stats.arp) + 700 > ArPCap Then
                    totalScore += ((ArPCap - myTotalStats(stats.arp)) * 0.1 * weights.arp)
                Else
                    totalScore += (700 * 0.1 * weights.arp)
                End If
            End If
            If DeathbringersWillN = True And DeathbringersWillNArP = True Then
                If myTotalStats(stats.arp) + 600 > ArPCap Then
                    totalScore += ((ArPCap - myTotalStats(stats.arp)) * 0.1 * weights.arp)
                Else
                    totalScore += (600 * 0.1 * weights.arp)
                End If
            End If

            ' For each of the two trinkets the correct value of the proc is added.
            ' For non cappable stats a full value is assumed, but for the cappable stats
            ' of hit, expertise, and ArP, the effective value of the proc is calculated and
            ' used.  This prevents the overvaluation of trinkets that provide a proc of a cappable
            ' stat, particularly ArP trinkets such as Grim Toll or Mjolnir Runestone.
            For loopvar = slotIdx.trinket1 To slotIdx.trinket2
                With items(11, itemIDs(loopvar))
                    Select Case .procstat
                        Case Item.stats.str
                            totalScore += (.procvalue * .procuptime * weights.str)
                        Case Item.stats.ap
                            totalScore += (.procvalue * .procuptime * weights.ap)
                        Case Item.stats.crit
                            totalScore += (.procvalue * .procuptime * weights.crit)
                        Case Item.stats.haste
                            totalScore += (.procvalue * .procuptime * weights.haste)
                        Case Item.stats.arp
                            If myTotalStats(stats.arp) + .procvalue > ArPCap Then
                                totalScore += ((ArPCap - myTotalStats(stats.arp)) * .procuptime * weights.arp)
                            Else
                                totalScore += (.procvalue * .procuptime * weights.arp)
                            End If
                        Case Item.stats.agi
                            totalScore += (.procvalue * .procuptime * weights.agi)
                        Case Item.stats.hit
                            If myTotalStats(stats.hit) < HitCap Then
                                If myTotalStats(stats.hit) + .procvalue <= HitCap Then
                                    totalScore += (.procvalue * .procuptime * weights.hit)
                                Else
                                    If myTotalStats(stats.hit) + .procvalue <= SpellCap Then
                                        totalScore += ((HitCap - myTotalStats(stats.hit)) * .procuptime * weights.hit) + ((myTotalStats(stats.hit) + .procvalue - HitCap) * .procuptime * weights.sphit)
                                    Else
                                        totalScore += ((myTotalStats(stats.hit) + .procvalue - SpellCap) * .procuptime * weights.exhit)
                                        totalScore += ((SpellCap - HitCap) * .procuptime * weights.sphit)
                                        totalScore += ((HitCap - myTotalStats(stats.hit)) * .procuptime * weights.hit)
                                    End If
                                End If
                            ElseIf myTotalStats(stats.hit) < SpellCap Then
                                If myTotalStats(stats.hit) + .procvalue < SpellCap Then
                                    totalScore += .procvalue * .procuptime * weights.sphit
                                Else
                                    totalScore += ((SpellCap - myTotalStats(stats.hit)) * .procuptime * weights.sphit)
                                    totalScore += ((myTotalStats(stats.hit) + .procvalue - SpellCap) * .procuptime * weights.exhit)
                                End If
                            Else
                                totalScore += .procvalue * .procuptime * weights.exhit
                            End If
                        Case Item.stats.exp
                            If myTotalStats(stats.exp) + .procvalue > ExpCapRating Then
                                totalScore += ((ExpCapRating - myTotalStats(stats.exp)) * .procuptime * weights.exp)
                            Else
                                totalScore += (.procvalue * .procuptime * weights.exp)
                            End If
                    End Select
                End With
            Next

            ' The total hit rating for the set is converted into a score, keeping in mind
            ' both the melee and spell hit caps and the various valuations of hit at these levels.
            If myTotalStats(stats.hit) <= HitCap Then
                totalScore += (myTotalStats(stats.hit) * weights.hit)
            ElseIf myTotalStats(stats.hit) <= SpellCap Then
                totalScore += ((HitCap * weights.hit) + ((myTotalStats(stats.hit) - HitCap) * weights.sphit))
            Else
                totalScore += ((HitCap * weights.hit) + ((SpellCap - HitCap) * weights.sphit) + ((myTotalStats(stats.hit) - SpellCap) * weights.exhit))
            End If

            ' The total expertise rating for the set is converted into a score, keeping in mind
            ' that expertise has no value after the dodge cap for melee DPS.  Note that expertise
            ' is not graduated - recent testing shows that it is in fact a continuous function rather
            ' than a stepwise function.
            If myTotalStats(stats.exp) <= ExpCapRating Then
                totalScore += (myTotalStats(stats.exp) * weights.exp)
            Else
                totalScore += (ExpCapRating * weights.exp)
            End If

            ' Variable definition for the maximum and minimum score that will be calculation for
            ' the set.
            Dim maxScore As Double, minScore As Double
            Dim budget As Double
            budget = numGemSockets * 20

            ' If hit or expertise caps have been forced, immediately use the budget as necessary
            ' to meet these caps, adjusting totalscore and totalstats as we go since this has to be relevant to
            ' all sets.  After this has been done, feed the remaining budget into the traditional
            ' optimizations on the standard weights and we'll be good to go.
            If minHit > 0 Or minExp > 0 Then
                ' If there is no way we can meet the budget, then exit the sub since
                ' this set can never ever be valid.
                If System.Math.Max(minHit - myTotalStats(stats.hit), 0) + System.Math.Max(minExp - myTotalStats(stats.exp), 0) > budget Then
                    Exit Sub
                Else
                    ' now increment score, decrement budget, and increment total stats for hit / exp
                    If myTotalStats(stats.exp) < minExp Then
                        budget -= (minExp - myTotalStats(stats.exp))
                        totalScore += ((minExp - myTotalStats(stats.exp)) * weights.exp)
                        myTotalStats(stats.exp) = minExp
                    End If
                    If myTotalStats(stats.hit) < minHit Then
                        budget -= (minHit - myTotalStats(stats.hit))
                        ' Adding hit score is more annoying, since we have to account
                        ' for melee and spell hit values.

                        ' If the cap is melee, it's not so hard.
                        If minHit = HitCap Then
                            totalScore += ((minHit - myTotalStats(stats.hit)) * weights.hit)
                            myTotalStats(stats.hit) = minHit
                        ElseIf minHit = SpellCap Then
                            ' this will handle the spellcap  Remember that if minhit is zero we can't ever
                            ' be less htan it, so we don't have to handle it.
                            If myTotalStats(stats.hit) < HitCap Then
                                totalScore += ((HitCap - myTotalStats(stats.hit)) * weights.hit)
                                myTotalStats(stats.hit) = HitCap
                                totalScore += ((minHit - myTotalStats(stats.hit)) * weights.sphit)
                                myTotalStats(stats.hit) = minHit
                            Else
                                ' already past spell cap
                                totalScore += ((minHit - myTotalStats(stats.hit)) * weights.sphit)
                                myTotalStats(stats.hit) = minHit
                            End If
                        End If
                    End If
                End If
            End If

            ' To calculate the minimum score, add a score equal to the best gem of a non-cappable stat
            ' in all sockets but one.  Assume a Nightmare Tear in the final socket and the worse of the two
            ' meta gems (RED) in the meta socket.  No set bonuses are counted.
            minScore = totalScore + (((System.Math.Max((budget - 20) / 20, 0)) * bestNonCapGemScore) + (21 * weights.agi))
            If budget - 20 >= 20 Then
                minScore += (10 * weights.str) + (10 * weights.agi)
            End If

            ' To calculate the maximum score, determine the total number of stat points available from gems.
            ' Fill up the gear set with the most valuable stat for each point, keeping in mind hit caps,
            ' expertise caps, etc.  Assume all set bonuses are met and the better of the two meta gems (CSD)
            ' is used.  Do not sacrifice any gems for the Nightmare Tear.

            maxScore = totalScore + (21 * weights.crit) ' Meta
            If weights.hit > bestNonCapWeight Then
                If weights.hit > weights.exp Then
                    If weights.exp > bestNonCapWeight Then
                        ' Hit > Exp > Best NonCap Stat
                        If myTotalStats(stats.hit) < HitCap Then
                            maxScore += ((HitCap - myTotalStats(stats.hit)) * weights.hit)
                            budget -= (HitCap - myTotalStats(stats.hit))
                            If myTotalStats(stats.exp) > ExpCapRating Then
                                maxScore += (budget * bestNonCapWeight)
                            Else
                                maxScore += ((ExpCapRating - myTotalStats(stats.exp)) * weights.exp)
                                budget -= (ExpCapRating - myTotalStats(stats.exp))
                                maxScore += (budget * bestNonCapWeight)
                            End If
                        Else
                            If myTotalStats(stats.exp) > ExpCapRating Then
                                maxScore += (budget * bestNonCapWeight)
                            Else
                                maxScore += ((ExpCapRating - myTotalStats(stats.exp)) * weights.exp)
                                budget -= (ExpCapRating - myTotalStats(stats.exp))
                                maxScore += (budget * bestNonCapWeight)
                            End If
                        End If
                    Else
                        ' Hit > Best NonCap Stat > Expertise
                        If myTotalStats(stats.hit) < HitCap Then
                            maxScore += ((HitCap - myTotalStats(stats.hit)) * weights.hit)
                            budget -= (HitCap - myTotalStats(stats.hit))
                            maxScore += (budget * bestNonCapWeight)
                        Else
                            maxScore += (budget * bestNonCapWeight)
                        End If
                    End If
                Else
                    ' Expertise > Hit > Best NonCap Stat
                    If myTotalStats(stats.exp) < ExpCapRating Then
                        maxScore += ((ExpCapRating - myTotalStats(stats.exp)) * weights.exp)
                        budget -= (ExpCapRating - myTotalStats(stats.exp))
                        If myTotalStats(stats.hit) < HitCap Then
                            maxScore += ((HitCap - myTotalStats(stats.hit)) * weights.hit)
                            budget -= (HitCap - myTotalStats(stats.hit))
                            maxScore += (budget * bestNonCapWeight)
                        Else
                            maxScore += (budget * bestNonCapWeight)
                        End If
                    Else
                        If myTotalStats(stats.hit) < HitCap Then
                            maxScore += ((HitCap - myTotalStats(stats.hit)) * weights.hit)
                            budget -= (HitCap - myTotalStats(stats.hit))
                            maxScore += (budget * bestNonCapWeight)
                        Else
                            maxScore += (budget * bestNonCapWeight)
                        End If
                    End If
                End If
            Else
                If weights.hit < weights.exp Then
                    If weights.exp < bestNonCapWeight Then
                        ' Best NonCap Stat > Expertise > Hit
                        maxScore += (budget * bestNonCapWeight)
                    Else
                        ' Expertise > Best NonCap Stat > Hit
                        If myTotalStats(stats.exp) > ExpCapRating Then
                            maxScore += (budget * bestNonCapWeight)
                        Else
                            maxScore += ((ExpCapRating - myTotalStats(stats.exp)) * weights.exp)
                            budget -= (ExpCapRating - myTotalStats(stats.exp))
                            maxScore += (budget * bestNonCapWeight)
                        End If
                    End If
                Else
                    ' Best NonCap Stat > Hit > Expertise
                    maxScore += (budget * bestNonCapWeight)
                End If
            End If
            For loopvar = 0 To numPieces - 1
                maxScore += socketBonusValue(loopvar)
            Next

            ' Maximum and minimum scores are compared against the previous best minimum.  If this value
            ' is exceeded, raise the AddCandidateSet event that tells the parent scope to add
            ' a set with the current IDs, scores, and enchants.  If the best minimum has changed,
            ' update it.
            If maxScore > bestMinScore Then
                RaiseEvent AddCandidateSet(itemIDs, minScore, maxScore, wEnch, hEnch, fEnch)
            End If
            If minScore > bestMinScore Then
                bestMinScore = minScore
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Function socketBonusValue(ByVal slotIndex As slotIdx) As Double
        ' This function determines the numeric value of the bonus of a item
        ' from sockets.  Full value from cappable stats is assumed
        ' since this is used for the calculation of max value.  slotIDX is
        ' translated to the item index type before performing the check.
        Try
            Dim newIndex As Integer
            If slotIndex = slotIdx.finger2 Or slotIndex = slotIdx.finger1 Then
                newIndex = 10
            ElseIf slotIndex = slotIdx.trinket2 Or slotIndex = slotIdx.trinket1 Then
                newIndex = 11
            ElseIf slotIndex = slotIdx.weapon2 Or slotIndex = slotIdx.weapon1 Then
                newIndex = 12
            Else
                newIndex = slotIndex
            End If
            Select Case items(newIndex, itemIDs(slotIndex)).socketstat
                Case Item.stats.agi
                    socketBonusValue = weights.agi * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.ap
                    socketBonusValue = weights.ap * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.arp
                    socketBonusValue = weights.arp * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.crit
                    socketBonusValue = weights.crit * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.exp
                    socketBonusValue = weights.exp * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.haste
                    socketBonusValue = weights.haste * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.hit
                    socketBonusValue = weights.hit * items(newIndex, itemIDs(slotIndex)).socketbonus
                Case Item.stats.none
                    socketBonusValue = 0
                Case Item.stats.str
                    socketBonusValue = weights.str * items(newIndex, itemIDs(slotIndex)).socketbonus
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function trans(ByVal slotindex As Integer) As Integer
        ' The translate function turns a slotindex (0 to 15) into an item index
        ' (0 to 12) for the purposes of checked the items referenced in the 
        ' itemIDs array.
        Try
            If slotindex > slotIdx.foot Then
                If slotindex = slotIdx.finger1 Or slotindex = slotIdx.finger2 Then
                    trans = 10
                ElseIf slotindex = slotIdx.trinket1 Or slotindex = slotIdx.trinket2 Then
                    trans = 11
                ElseIf slotindex = slotIdx.weapon1 Or slotindex = slotIdx.weapon2 Then
                    trans = 12
                End If
            Else
                trans = slotindex
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Sub calculateInternalProcs()
        ' This function will calculate the base proc value for all internally
        ' modeled items.  Any cappable stat (ArP proc etc) will be modeled later.

        ' Values are pulled from data_internalprocs.xml in the user's program files directory
        ' This is a huge pain in the ass, but it's better than nothing and fairly flexible,
        ' so I'm okay with it. Note that this is only run at the very beginning of the thread,
        ' so some readability by sacrificing speed is probably worth it (thus items being done separately etc)

        ' Set up the file to be read.  Note that the file WILL exist at this point, or a huge error will
        ' occur.
        Try
            Dim m_xmld As XmlDocument
            Dim m_nodelist As XmlNodeList
            Dim m_node As XmlNode
            Dim buildinfo As FileVersionInfo = FileVersionInfo.GetVersionInfo(Windows.Forms.Application.ExecutablePath)

            m_xmld = New XmlDocument()
            m_xmld.Load(Windows.Forms.Application.CommonAppDataPath & "\data_internalprocs.xml")
            m_nodelist = m_xmld.SelectNodes("/internalprocs")
            m_node = m_nodelist(0)

            ' Bryntroll, the Bone Arbiter.
            ' Stored value is AP equivalence for the proc. No other effects.
            Select Case primaryTree
                Case "Blood"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(0).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Unholy"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(1).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Frost"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(2).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            End Select

            ' Bryntroll, the Bone Arbiter (Heroic).
            ' Stored value is AP equivalence for the proc. No other effects.
            Select Case primaryTree
                Case "Blood"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(3).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Unholy"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(4).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Frost"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(5).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            End Select

            ' Ashen Band of Unmatched / Endless Vengeance
            ' Stored value is a decimal representing total uptime. No other effects.
            AshenRingEValue = 480 * weights.ap * Double.Parse(m_node.ChildNodes.Item(30).InnerText, System.Globalization.CultureInfo.InvariantCulture)

            ' Shadowmourne
            ' Stored value is both a constant amount of Strength representing the stacking buff
            ' and an Attack Power equivalent per-spec value for the damage proc.
            ShadowmourneValue = weights.str * Double.Parse(m_node.ChildNodes.Item(31).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            Select Case primaryTree
                Case "Blood"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(32).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(33).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(34).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select

            ' Deathbringer's Will
            ' Stored value gives three stats and a total uptime.
            ' Add non-ArP stats here and then flag to add ArP at the same time as other trinkets.
            Dim statIndex(0 To 2) As Integer
            Dim loopvar As Integer
            Select Case primaryTree
                Case "Blood"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(6).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(7).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(8).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(9).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(10).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(11).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(12).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(13).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(14).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(15).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(16).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(17).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select
            DeathbringersWillNValue = 0
            DeathbringersWillNArP = False
            For loopvar = 0 To 2
                Select Case statIndex(loopvar)
                    Case 0
                        ' None
                    Case 1
                        ' Strength
                        DeathbringersWillNValue += 600 * weights.str * (DeathbringersWillNUptime / 3)
                    Case 2
                        ' AP
                        DeathbringersWillNValue += 1200 * weights.ap * (DeathbringersWillNUptime / 3)
                    Case 3
                        ' Crit
                        DeathbringersWillNValue += 600 * weights.crit * (DeathbringersWillNUptime / 3)
                    Case 4
                        ' Haste
                        DeathbringersWillNValue += 600 * weights.haste * (DeathbringersWillNUptime / 3)
                    Case 5
                        'ArP
                        DeathbringersWillNArP = True
                    Case 6
                        'Agi
                        DeathbringersWillNValue += 600 * weights.agi * (DeathbringersWillNUptime / 3)
                End Select
            Next

            ' Deathbringer's Will (Heroic)
            ' Stored value gives three stats and a total uptime.
            ' Add non-ArP stats here and then flag to add ArP at the same time as other trinkets.
            Select Case primaryTree
                Case "Blood"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(18).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(19).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(20).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(21).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(22).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(23).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(24).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(25).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(26).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(27).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(28).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(29).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select
            DeathbringersWillHValue = 0
            DeathbringersWillHArP = False
            For loopvar = 0 To 2
                Select Case statIndex(loopvar)
                    Case 0
                        ' None
                    Case 1
                        ' Strength
                        DeathbringersWillHValue += 700 * weights.str * (DeathbringersWillNUptime / 3)
                    Case 2
                        ' AP
                        DeathbringersWillHValue += 1400 * weights.ap * (DeathbringersWillNUptime / 3)
                    Case 3
                        ' Crit
                        DeathbringersWillHValue += 700 * weights.crit * (DeathbringersWillNUptime / 3)
                    Case 4
                        ' Haste
                        DeathbringersWillHValue += 700 * weights.haste * (DeathbringersWillNUptime / 3)
                    Case 5
                        'ArP
                        DeathbringersWillHArP = True
                    Case 6
                        'Agi
                        DeathbringersWillHValue += 700 * weights.agi * (DeathbringersWillNUptime / 3)
                End Select
            Next
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub
End Class